﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GameLibrary.Map
{
    using Interfaces;
    using Events;
    [Serializable]
    public class MapObjects
    {
        #region variable

        /// <summary>
        /// Map object id to object.
        /// uint ID
        /// T object
        /// </summary>
        private Dictionary<uint, IMapObject> IDObjectMap = new Dictionary<uint, IMapObject>();

        public int Count
        {
            get { return this.IDObjectMap.Count; }
        }
       
        /// <summary>
        /// Map position to object ID.
        /// uint in hashset object ID.
        /// </summary>
        private Dictionary<Position, HashSet<uint>> PositionIDMap = new Dictionary<Position, HashSet<uint>>();
       
        /// <summary>
        /// Map ownerID to object ID.
        /// 1st uint ownerID.
        /// 2nd object ID.
        /// </summary>
        private Dictionary<uint, HashSet<uint>> OwnerIDToIDMap = new Dictionary<uint, HashSet<uint>>();
        
        #endregion

        #region constructor

        public MapObjects() { }

        public MapObjects(MapObjects objs)
        {
            this.IDObjectMap = new Dictionary<uint, IMapObject>(objs.IDObjectMap);
            this.PositionIDMap = new Dictionary<Position, HashSet<uint>>(objs.PositionIDMap);
            this.OwnerIDToIDMap = new Dictionary<uint, HashSet<uint>>(objs.OwnerIDToIDMap);
        }

        #endregion

        #region this

        /// <summary>
        /// return null if not contained
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public IMapObject this[uint id]
        {
            get
            {
                if (IDObjectMap.ContainsKey(id) == true)
                    return IDObjectMap[id];
                else
                    return null;
            }
        }

        #endregion

        #region add

        /// <summary>
        /// Add object into mapobjects.
        /// Return false when there exist a object with same id.
        /// </summary>
        /// <param name="obj"></param>
        internal bool Add(IMapObject obj)
        {
            if (this.IDObjectMap.ContainsKey(obj.ID) == false)
            { 
                this.AddToPositionIDMap(obj);
                this.AddToOwnerIDTIDMap(obj); 
                this.IDObjectMap.Add(obj.ID, obj);   
                return true;
            }
            else
                return false;
        }

        private void AddToPositionIDMap(IMapObject obj)
        {
            if (this.PositionIDMap.ContainsKey(obj.Location) == false)
                this.PositionIDMap.Add(obj.Location, new HashSet<uint> { obj.ID });
            else
                this.PositionIDMap[obj.Location].Add(obj.ID);
        }

        private void AddToOwnerIDTIDMap(IMapObject obj)
        {
            if (this.OwnerIDToIDMap.ContainsKey(obj.OwnerID) == false)
                this.OwnerIDToIDMap.Add(obj.OwnerID, new HashSet<uint> { obj.ID });
            else
                this.OwnerIDToIDMap[obj.OwnerID].Add(obj.ID);
        }

        #endregion

        #region delete

        /// <summary>
        /// Remove obj from all map relations.
        /// Return true when obj exist and removed.
        /// Else return false.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        internal bool Remove(uint id)
        {
            if (this.IDObjectMap.ContainsKey(id) == true)
            {
                this.RemoveFromOwnerIDTIDMap(id);
                this.DeleteFromPositionIDMap(id);                   
                this.IDObjectMap.Remove(id);
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// Delete obj id from owner id t id map.
        /// Delete owner id if item it contains is zero.
        /// </summary>
        /// <param name="id"></param>
        private void RemoveFromOwnerIDTIDMap(uint id)
        {
            this.OwnerIDToIDMap[this.IDObjectMap[id].OwnerID].Remove(id);

            if (this.OwnerIDToIDMap[this.IDObjectMap[id].OwnerID].Count <= 0)
                this.OwnerIDToIDMap.Remove(this.IDObjectMap[id].OwnerID);
        }

        /// <summary>
        /// Delete obj id from position id map.
        /// Delete position if its id list count is 0.
        /// </summary>
        /// <param name="id"></param>
        private void DeleteFromPositionIDMap(uint id)
        {
            this.PositionIDMap[this.IDObjectMap[id].Location].Remove(id);

            if (this.PositionIDMap[this.IDObjectMap[id].Location].Count <= 0)
                this.PositionIDMap.Remove(this.IDObjectMap[id].Location);
        }

        /// <summary>
        /// Clear every thing.
        /// </summary>
        internal void DeleteAll()
        {
            this.IDObjectMap.Clear();
            this.OwnerIDToIDMap.Clear();
            this.PositionIDMap.Clear();
        }

        #endregion

        #region Get

        internal IMapObject GetObjById(uint id)
        {
            return this[id];
        }
        internal int GetObjsCountOfAOwnerID(uint ownerID)
        {
            return this.OwnerIDToIDMap[ownerID].Count;
        }

        /// <summary>
        /// Return all objs on this position as a list.
        /// If not found return null;
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        internal List<IMapObject> GetObjsByPosition(Position pos)
        {
            if (this.PositionIDMap.ContainsKey(pos) == false)
                return null;
            List<IMapObject> list = new List<IMapObject>();
            foreach (uint id in this.PositionIDMap[pos])
            {
                list.Add(this.IDObjectMap[id]);
            }
            return list;
        }

        /// <summary>
        /// Return all objs have same ownerID value.
        /// </summary>
        /// <param name="ownerID"></param>
        /// <returns></returns>
        internal List<IMapObject> GetObjsByOwnerID(uint ownerID)
        {
            if (this.OwnerIDToIDMap.ContainsKey(ownerID) == false)
                return null;
            List<IMapObject> list = new List<IMapObject>();
            foreach (uint id in this.OwnerIDToIDMap[ownerID])
                list.Add(this.IDObjectMap[id]);
            return list;
        }

        internal List<uint> GetOwnerIDs()
        {
            return new List<uint>(this.OwnerIDToIDMap.Keys);
        }

        #endregion

        #region contains

        internal bool ContainsID(uint id)
        {
            return this.IDObjectMap.ContainsKey(id);
        }

        internal bool ContainsPosition(Position pos)
        {
            return this.PositionIDMap.ContainsKey(pos);
        }

        internal bool ContainsOwner(uint id)
        {
            return this.OwnerIDToIDMap.ContainsKey(id);
        }

        #endregion

        #region values

        /// <summary>
        /// Get all object stored
        /// </summary>
        public IEnumerable<IMapObject> Values
        {
            get
            {
                return this.IDObjectMap.Values;
            }
        }

        #endregion
    }
}
